package com.gitee.wsl.time

import com.gitee.wsl.ext.base.ifValue
import kotlinx.datetime.DateTimeUnit
import kotlinx.datetime.DayOfWeek
import kotlinx.datetime.LocalDate
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.until
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlin.time.Duration.Companion.days
import kotlin.time.ExperimentalTime

/**
 * Represents the formatting for the counting of weeks within a month or year.
 *
 * @property [minimalDaysInFirstWeek] Gets the minimal number of days in the first week.
 * The number of days considered defining the first week of a month or year varies by culture. For example, the
 * ISO-8601 requires 4 days (more than half a week) to be present before counting the first week.
 *
 * @property [firstDayOfWeek] Gets the first day-of-week.
 * The first day-of-week varies by culture. For example, the US uses Sunday, while France and the ISO-8601 standard use
 * Monday. This method returns the first day using the standard DayOfWeek enum.
 */
@ExperimentalTime
@Serializable
data class WeekFormat(
    @SerialName(value = "minimal_days_in_first_week")
    val minimalDaysInFirstWeek: Int = 4,

    @SerialName(value = "first_day_of_week")
    val firstDayOfWeek: DayOfWeek = DayOfWeek.MONDAY
) {
    companion object{
        val default = WeekFormat()
    }
}


// ISO 8601 (first week is the one after 1 containing a thursday)
@OptIn(ExperimentalTime::class)
fun Year.first(dayOfWeek: DayOfWeek): LocalDateTime {
    val start = LocalDateTime(this, 1, 1)
    var n = 0
    while (true) {
        val time = (start + n.days)
        if (time.dayOfWeek == dayOfWeek) return time
        n++
    }
}

@OptIn(ExperimentalTime::class)
val LocalDateTime.weekOfYear0: Int
    get() = date.weekOfYear0

val LocalDateTime.weekOfYear1: Int get() = weekOfYear0 + 1

@OptIn(ExperimentalTime::class)
val LocalDate.weekOfYear0: Int
    get() {
        val firstThursday = this.Year.first(DayOfWeek.THURSDAY)
        val offset = firstThursday.dayOfMonth - 3
        return (dayOfYear - offset) / 7
    }

val LocalDate.weekOfYear1: Int get() = weekOfYear0 + 1


@OptIn(ExperimentalTime::class)
fun LocalDate.weekOfYear(weekFormat: WeekFormat = WeekFormat.default): Int //= ApplicationConfig.systemTimeProvider.weekOfYear(this,weekFormat)
   =  (weekFormat.firstDayOfWeek == DayOfWeek.SUNDAY).ifValue(weekOfYear0,weekOfYear1)

@OptIn(ExperimentalTime::class)
fun LocalDate.weekOfMonth(weekFormat: WeekFormat = WeekFormat.default): Int{ TODO()} //= ApplicationConfig.systemTimeProvider.weekOfMonth(this,weekFormat)

@OptIn(ExperimentalTime::class)
fun LocalDateTime.weekOfYear(weekFormat: WeekFormat = WeekFormat.default): Int =
    this.date.weekOfYear(weekFormat = weekFormat)

@ExperimentalTime
fun LocalDateTime.weekOfMonth(weekFormat: WeekFormat = WeekFormat.default): Int =
    this.date.weekOfMonth(weekFormat = weekFormat)

fun LocalDate.weeksUntil(other: LocalDate): Int = until(other, DateTimeUnit.WEEK).toInt()